/* 
 * File:   FieldSolver.cpp
 * Author: Lucky
 * 
 * Created on 5. říjen 2011, 11:06
 */

#include<iostream>

#include "FieldSolver.h"
#include "Chessboard.h"
#include "Field.h"
#include "Library.h"
#include "Result.h"
#include <mpi.h>

#include <stdlib.h>
#include <time.h>
//#include <pthread.h>

FieldSolver::FieldSolver(int width, int height, int q, int* coords, Field* field, int pCount, int myRank) {
    m_width = width;
    m_height = height;
    m_myRank = myRank;
    m_q = q;
    m_coords = coords;
    m_field = field;
    m_sendedToLowerP = false;
    m_tokenSended = false;
    m_state = STATE_WORKING;
    std::cout << "P" << m_myRank << " fieldSolver constructor - vytvarim chessboard" << std::endl << std::flush;

    //    std::cout << "P" << m_myRank << " ma data, vytvari board (" << m_width << ", " << m_height << ", [" << std::flush;
    //    for (int i = 0; i < 2*m_q; i++) std::cout << m_coords[i] << ", " << std::flush;
    //    std::cout << "], " << m_q << ")" << std::endl << std::flush;

    m_board = new Chessboard(m_width, m_height, m_coords, m_q, myRank);
    Library::clickChessboard(m_board, m_field);
    std::cout << "P" << m_myRank << " fieldSolver constructor - chessboard vytvoren" << std::endl << std::flush;
    m_res = 0; //0 je NULL
    m_pCount = pCount;

    srand(myRank);
}

FieldSolver::FieldSolver(const FieldSolver& orig) {
}

FieldSolver::~FieldSolver() {
    delete m_field;
}

void FieldSolver::compute() {
    std::cout << "P" << m_myRank << " compute start" << std::endl << std::flush;
    int stepCounter = 0; //Tohle nam pocita pocet projetch stavu
    int tempCounter = 0; //Tohle pouzivame na pocet cyklu neceho, kazdy stav si s tim dela cosi :)

    while (true) {
        if (m_state == STATE_WORKING) {
            do {
                //        std::cout << "P" << m_myRank << " Fieldsolver beh "<<counter << std::endl << std::flush;
                ++stepCounter;
                if (stepCounter % 100 == 0) {
                    this->verifyMsgs();
                }

                if (m_board->getOnLightsCount() == 0 && (m_res == 0 || m_res->getSteps() > m_field->getSwitchCount())) {
                    std::cout << "P" << m_myRank << " Mam lepsi vysledek!" << std::endl << std::flush;
                    new Result(0, 0);
                    int test = m_field->getSwitchCount();
                    std::cout << "P" << m_myRank << " Pocet sepnuti je" << test << std::endl << std::flush;
                    int* coords = new int[2 * m_field->getSwitchCount()];
                    //std::cout<<"SC"<<m_field->getSwitchCount()<<std::endl;
                    int j = 0;
                    for (int i = 0; i < m_field->m_length; i++) {

                        if (m_field->m_field[i]) {
                            std::cout << "P" << m_myRank << " sepnuti" << std::endl << std::flush;
                            if (test <= 0) {
                                std::cerr << "P" << m_myRank << " KURVA, je tu moc sepnutí" << std::endl << std::flush;
                            }
                            test--;
                            //std::cout << Library::getXCoord(m_width, i) << "X" << Library::getYCoord(m_width, i) << std::endl;
                            coords[j++] = Library::getXCoord(m_width, i);
                            coords[j++] = Library::getYCoord(m_width, i);
                        }
                    }
                    if (m_res != 0) {
                        delete m_res;
                    }
                    m_res = new Result(m_field->getSwitchCount(), coords);
                    this->sendResult();
                }
            } while (increaseField());

            std::cout << "P" << m_myRank << " pocetProjetychKroku: " << stepCounter << std::endl << std::flush;
            if (m_res != 0) {
                std::cout << "P" << m_myRank << " ";
                m_res->printCoords();
            } else {
                std::cout << "P" << m_myRank << " Nenalezeno reseni" << std::endl << std::flush;
            }
            m_state = STATE_NOWORK;
        } else if (m_state == STATE_WANTWORK) {
            this->verifyMsgs();
        } else if (m_state == STATE_NOWORK) {
            //sleep(1);
            if (m_myRank == 0 ) {
                if (m_pCount == 1) {
                    m_state = STATE_FINALIZE;
                } else if (m_tokenSended == false) {
                    int token = 0; //0 je bila
                    std::cout << "P" << m_myRank << " posilam peska procesoru 1" << std::endl << std::flush;
                    MPI_Send(&token, 1, MPI_INT, 1, TAG_ADUV, MPI_COMM_WORLD);
                    m_tokenSended = true;
                } else {
                    this->verifyMsgs();
                }
            } else {
                this->verifyMsgs();
                
                int milisec = 10; // length of time to sleep, in miliseconds
                struct timespec req = {0};
                req.tv_sec = 0;
                req.tv_nsec = milisec * 1000000L;
                nanosleep(&req, (struct timespec *)NULL);
                
                if (m_state != STATE_NOWORK) continue;
                
                ++tempCounter;
                if (tempCounter % 5 == 0) {
                        this->askForWork();
                }
            }
        } else if (m_state == STATE_STOP) {
            this->verifyMsgs();
            if (m_myRank == 0) {
                std::cout << "P " << m_myRank << " procesor rozesila stop" << std::endl << std::flush;
                int noMsg = 3; //Neco proste posleme
                for (int i = 1; i < m_pCount; i++) {
                    MPI_Send(&noMsg, 1, MPI_INT, i, TAG_STOP, MPI_COMM_WORLD);
                }
                m_state = STATE_FINALIZE;
            }
        } else if (m_state == STATE_FINALIZE) {
            if (m_myRank == 0) {
                std::cout << "P " << m_myRank << " procesor rozesila ukonceni" << std::endl << std::flush;
                int noMsg = 3; //Neco proste posleme
                for (int i = 1; i < m_pCount; i++) {
                    MPI_Send(&noMsg, 1, MPI_INT, i, TAG_FINALIZE, MPI_COMM_WORLD);
                }
                
                std::cout << "AUTORITATIVNI ODPOVED: ";
                if (m_res != 0) {
                    m_res->printCoords();
                } else {
                    std::cout << "Nenalezeno reseni" << std::endl << std::flush;
                }
            }
            return;
        } else {
            std::cerr << "Neznamy stav: " << m_state << std::endl << std::flush;
        }
    }
}

bool FieldSolver::increaseField() {
    //    std::cout << "P" << m_myRank << " increaseField()" << std::endl << std::flush; 
    int fieldLength = m_field->m_length;
    int point = fieldLength - 1; //posledni polozka fieldu
    //    std::cout << "P" << m_myRank << " increaseField() zacinam while" << std::endl << std::flush; 
    while (point > m_field->m_end && point >= 0) {
        if (m_field->m_field[point] == false) {
            //            std::cout << "P" << m_myRank << " increaseField() " << std::endl << std::flush; 
//            if (m_res != 0 && m_field->getSwitchCount() >= m_res->getSteps() - 1) {
//                --point;
//                continue;
//            }
            m_field->setState(point, true); //m_field->m_field[point] = true;
            m_board->pressLight(Library::getXCoord(m_width, point), Library::getYCoord(m_width, point));
            for (int i = point + 1; i < fieldLength; i++) {
                if (m_field->m_field[i]) {
                    m_board->pressLight(Library::getXCoord(m_width, i), Library::getYCoord(m_width, i));
                }
                m_field->setState(i, false); //m_field->m_field[i] = false;
            }
            if (m_res != 0 && m_field->getSwitchCount() >= m_res->getSteps()) {
                --point;
                continue;
            }
            return true;
        } else {
            --point;
        }
    }

    return false;
}

void FieldSolver::verifyMsgs() {
    if(m_pCount>1){
    MPI_Status status;
    int flag;

    //Tag 1 (TAG_RESULT) je pro novy lepsi vysledek (cislo jako pocet kroku)
    MPI_Iprobe(MPI_ANY_SOURCE, TAG_RESULT, MPI_COMM_WORLD, &flag, &status);
    if (flag) {
        /* receiving message by blocking receive */
        int coordLength = 0;
        MPI_Recv(&coordLength, 1, MPI_INT, MPI_ANY_SOURCE, TAG_RESULT, MPI_COMM_WORLD, &status);
        int sender = status.MPI_SOURCE;
        int* coords = new int[2 * coordLength];
        MPI_Recv(coords, 2 * coordLength, MPI_INT, sender, TAG_RESULT, MPI_COMM_WORLD, &status);
        Result* newRes = new Result(coordLength, coords);
        if (this->m_res == 0 || this->m_res->getSteps() > newRes->getSteps()) {
            if (this->m_res != 0) {
                delete this->m_res;
            }
            this->m_res = newRes;
        }
        std::cout << "P" << m_myRank << " prijal jsem od P" << sender << std::endl << std::flush;
    }

    //Tag 2 (TAG_WANTWORK) je pro prijmuti zadosti o praci
    MPI_Iprobe(MPI_ANY_SOURCE, TAG_WANTWORK, MPI_COMM_WORLD, &flag, &status);
    if (flag) {
        int msg = 0;
        MPI_Recv(&msg, 1, MPI_INT, MPI_ANY_SOURCE, TAG_WANTWORK, MPI_COMM_WORLD, &status);
        int sender = status.MPI_SOURCE;
        this->processAskWork(sender);
    }

    //Tag 3 (TAG_NOWORK) je pro prijmuti oznameni o tom, ze nekdo pro nas nema praci
    MPI_Iprobe(MPI_ANY_SOURCE, TAG_NOWORK, MPI_COMM_WORLD, &flag, &status);
    if (flag) {
        int msg = 0;
        MPI_Recv(&msg, 1, MPI_INT, MPI_ANY_SOURCE, TAG_NOWORK, MPI_COMM_WORLD, &status);
        int sender = status.MPI_SOURCE;
        m_state = STATE_NOWORK;
        std::cout << "P " << m_myRank << " procesor " << sender << " pro nas nema praci" << std::endl << std::flush;
    }

    //Tag 4 (TAG_SENDWORK) je pro prijmuti oznameni o tom, ze nekdo pro nas MA praci
    MPI_Iprobe(MPI_ANY_SOURCE, TAG_SENDWORK, MPI_COMM_WORLD, &flag, &status);
    if (flag) {
        delete m_field;
        int sourceP = status.MPI_SOURCE;
        Library::receiveStack(sourceP, &m_field, m_width * m_height);

        std::cout << "P " << m_myRank << " procesor " << sourceP << " pro nas MA praci" << std::endl << std::flush;

        m_board = new Chessboard(m_width, m_height, m_coords, m_q, m_myRank);
        Library::clickChessboard(m_board, m_field);
        m_state = STATE_WORKING;
    }

    //Tag 5 (TAG_ADUV) je pro prijmuti ADUV peska
    MPI_Iprobe(MPI_ANY_SOURCE, TAG_ADUV, MPI_COMM_WORLD, &flag, &status);
    if (flag) {
        int sourceP = status.MPI_SOURCE;
        int token = 0; //0 je bilej
        MPI_Recv(&token, 1, MPI_INT, MPI_ANY_SOURCE, TAG_ADUV, MPI_COMM_WORLD, &status);
         std::cout << "P " << m_myRank << " procesor " << sourceP << " posila peska "<<token << std::endl << std::flush;
        if (m_state == STATE_WORKING || m_sendedToLowerP) {
            m_sendedToLowerP = false;
            token = 1;
        }
        if (m_myRank == 0) {
            m_tokenSended = false;
            if (token == 0) {
                m_state = STATE_STOP;
            } else {
                this->askForWork();
            }
        } else {
            std::cout << "P" << m_myRank << " procesor posila peska dal "<<token << std::endl << std::flush;
            MPI_Send(&token, 1, MPI_INT, (m_myRank + 1) % m_pCount, TAG_ADUV, MPI_COMM_WORLD);
        }
    }

    //Tag 6 (TAG_STOP) je pro zastaveni prace, pouze budeme naslouchat
    MPI_Iprobe(MPI_ANY_SOURCE, TAG_STOP, MPI_COMM_WORLD, &flag, &status);
    if (flag) {
        int sourceP = status.MPI_SOURCE;
        int token = 0; //0 je bilej
        MPI_Recv(&token, 1, MPI_INT, MPI_ANY_SOURCE, TAG_STOP, MPI_COMM_WORLD, &status);
        std::cout << "P" << m_myRank << " dostal jsem stop" << std::endl << std::flush;
        m_state = STATE_STOP;
    }
    
    //Tag 7 (TAG_FINALIZE) je pro KONEC
    MPI_Iprobe(MPI_ANY_SOURCE, TAG_FINALIZE, MPI_COMM_WORLD, &flag, &status);
    if (flag) {
        int sourceP = status.MPI_SOURCE;
        int token = 0; //0 je bilej
        MPI_Recv(&token, 1, MPI_INT, MPI_ANY_SOURCE, TAG_FINALIZE, MPI_COMM_WORLD, &status);
        std::cout << "P" << m_myRank << " dostal jsem ukonceni" << std::endl << std::flush;
        m_state = STATE_FINALIZE;
    }
}
}

void FieldSolver::sendResult() {
    for (int i = 0; i < m_pCount; i++) {
        if (i != m_myRank) {
            std::cout << "P" << m_myRank << " jdu posilat vysledky procesoru " << i << std::endl << std::flush;
            int steps = m_res->getSteps();
            MPI_Send(&steps, 1, MPI_INT, i, TAG_RESULT, MPI_COMM_WORLD);
            int* coords = m_res->getCoords();
            MPI_Send(coords, 2 * steps, MPI_INT, i, TAG_RESULT, MPI_COMM_WORLD);
        }
    }
}

void FieldSolver::askForWork() {
    int pToAsk = 0;
    do {
        pToAsk = rand() % m_pCount;
    } while (pToAsk == m_myRank);

    std::cout << "P " << m_myRank << " zadam o praci procesor" << pToAsk << std::endl << std::flush;
    int msg = 1;
    MPI_Send(&msg, 1, MPI_INT, pToAsk, TAG_WANTWORK, MPI_COMM_WORLD);

    m_state = STATE_WANTWORK;
}

void FieldSolver::processAskWork(int sender) {
    std::cout << "P " << m_myRank << " prijmul jsem zadost o praci od procesoru " << sender << std::endl << std::flush;
    if (m_state == STATE_WORKING) {
        Field* newStack = new Field(m_field->m_length);
        bool res = Library::cutStack(m_field, newStack);
        if (res) { //Máme dost práce
            Library::sendStack(newStack, sender);
            return;
        }
    }
    Library::sendNoWork(sender); //Nemám práci

}


